package com.kepler.social.app.cases;

import com.kepler.social.app.port.UserRegisterPort;
import com.kepler.social.common.Time;
import com.kepler.social.common.constants.BusinessState;
import com.kepler.social.common.constants.RedisKeyConst;
import com.kepler.social.common.constants.TipsConst;
import com.kepler.social.common.exception.ServiceException;
import com.kepler.social.common.rule.SmsRule;
import com.kepler.social.common.utils.PasswordUtil;
import com.kepler.social.common.utils.QxcUuid;
import com.kepler.social.common.utils.StringExtendsUtils;
import com.kepler.social.common.utils.ValidateAssert;
import com.kepler.social.domain.support.sms.SmsBo;
import com.kepler.social.domain.user.account.UserAccountDomainService;
import com.kepler.social.domain.user.account.consts.CommonConst;
import com.kepler.social.domain.user.security.UserSecurityAggregate;
import com.kepler.social.domain.user.security.UserSecurityDomainService;
import com.kepler.social.domain.user.security.UserSecurityRepository;
import com.kepler.social.generic.MailHelper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class UserRegisterCase implements UserRegisterPort {

    private final int passwordLength = 10;

    @Value("${kepler.verif-mail-url}")
    private String verifMailUrl;

    @Resource
    private MailHelper mailHelper;
    @Resource
    private RedissonClient redissonClient;
    /**
     * 注意 用的是一套密码 如果变了 一定要改变
     * kepler-social-web com.kepler.social.web.configuration.SecuritySubUnitConfig#passwordEncoder()
     */
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private TemplateEngine templateEngine;
    @Resource
    private SmsBo smsBo;
    @Resource
    private UserSecurityDomainService userSecurityDomainService;
    @Resource
    private UserSecurityRepository userSecurityRepository;
    @Resource
    private UserAccountDomainService userAccountDomainService;

    /**
     * @param email 电子邮件
     *              todo opt 加入限流
     */
    @Override
    public void sendVerifMail(String email) {
        ValidateAssert.isTrue(StringExtendsUtils.isEmail(email), TipsConst.incorrect_email_format);

        UserSecurityAggregate aggregate = userSecurityRepository.findByEmail(email);
        aggregate.checkEmailCanBeRegistered();

        QxcUuid qxcUuid = QxcUuid.fastUUID();
        log.info("verifMailSend  mail {} qxcUuid {}", email, qxcUuid);

        Time time = new Time(CommonConst.expireTimeVerifMailWhenRegister, TimeUnit.SECONDS);
        redisTemplate.opsForValue().set(RedisKeyConst.EMAIL_VERIF_PREFIX + qxcUuid, email, time.getTime(), time.getUnit());
        Context context = new Context();
        context.setVariable("verifMailUrl", verifMailUrl);
        context.setVariable("token", qxcUuid);
        context.setVariable("hours", time.toHours());
        context.setVariable("unitDesc", "小时");
        String mail = templateEngine.process("register_verif_mail_template.html", context);
        mailHelper.sendHtmlMail(email, "注册验证邮件", mail);
    }

    /**
     * 跨库事务:
     * 用的是最终一致性方案
     * 1、email
     * 如果不存在，正在进行email和password的创建
     * 如果状态未验证，拿出userId即可，因为这一个阶段成功了，说明后续操作失败，只需补操作哦
     * 如果其他状态，那么表示已经被成功验证过了，不能再使用，即便这个邮箱账号被废弃了。
     * 2、user
     * 如果存在，什么操作都不用做，说明是后续操作失败，只需补操作即可
     * 如果不存在，正常生成即可
     * 失败没关系，用户再操作一次就可以
     * 3、email
     * 修改状态为使用中
     * 失败没关系，用户再操作一次就可以
     *
     * @param token token
     */
    @Override
    public void verifyVerifMail(String token) {
        ValidateAssert.isTrue(StringUtils.isNoneBlank(token), TipsConst.email_verify_token_empty);
        String email = (String) redisTemplate.opsForValue().get(RedisKeyConst.EMAIL_VERIF_PREFIX + token);
        ValidateAssert.isTrue(StringUtils.isNotBlank(email), TipsConst.email_verify_token_expire);
        // 分布式锁定
        final RLock lock = redissonClient.getLock(RedisKeyConst.MAIL_LOCK_PREFIX + email);
        try {
            boolean res = lock.tryLock();
            if (!res) {
                throw new ServiceException(BusinessState.DUPLICATE_REQUESTS);
            }
            // 预创建用户安全
            String password = PasswordUtil.randomPassword(passwordLength);
            UserSecurityAggregate aggregate = userSecurityDomainService.preCreateOfEmail(email, passwordEncoder.encode(password));
            // 创建用户账号
            userAccountDomainService.createOfEmail(email, aggregate.getRoot().getUserId());
            // 用户安全创建完成
            userSecurityDomainService.finishCreateOfEmail(aggregate);

            // 发送成功邮件 这个需要计入一个记录 以便错误之后进行重新发送
            // 或者通过event的方式
            Context context = new Context();
            context.setVariable("userId", aggregate.getRoot().getUserId());
            context.setVariable("accountType", "邮箱");
            context.setVariable("account", email);
            context.setVariable("password", password);
            String mail = templateEngine.process("register_success_mail_template.html", context);
            mailHelper.sendHtmlMail(email, "注册成功", mail);

            //删除key,防止用户重复点击
            redisTemplate.delete(RedisKeyConst.EMAIL_VERIF_PREFIX + token);
        } finally {
            if (null != lock && lock.isLocked()) { // 是否还是锁定状态
                if (lock.isHeldByCurrentThread()) {//是否是当前执行线程的锁
                    lock.unlock(); // 释放锁
                }
            }
        }
    }

    /**
     * @param mobilePhoneNumber
     * @opt 1、防止用户频繁输入短信验证码，需验证码发送限制。@todo
     * 2、如果怕给db带来压力，可以设置限流或者熔断，但是千万不要加入
     * boolean f = redisTemplate.opsForValue().setIfAbsent(RedisKeyConst.PHONE_CAPTCHA_Obtain_LOCK_PREFIX + phoneNumber,
     * 1, 45, TimeUnit.SECONDS);
     * if(!f){
     * throw new ServiceException(BusinessState.DUPLICATE_REQUESTS);
     * }
     * 因为如果加上之后系统挂了，用户请求另一个服务，会告知用户请点击，用户会认为系统很傻逼
     * 3、查找手机看是否被注册，如果未被注册或者已被废弃，可以发送验证码
     * 如果未注册完成，不能在这里实现完成，然后告诉用户，你已经成功注册。用户会懵B的。因为这里是验证码发送，不是注册
     */
    @Override
    public void sendMobilePhoneRegisterCaptcha(Integer countryCallingCode, final Long mobilePhoneNumber) {
        log.info("sendRegisterCaptcha param phoneNumber {}", mobilePhoneNumber);
        smsBo.sendMobilePhoneCaptcha(SmsRule.defaultCcc(countryCallingCode), mobilePhoneNumber,
                new Time(CommonConst.expireTimeCaptcha,TimeUnit.SECONDS),
                () -> {
                    UserSecurityAggregate aggregate =
                            userSecurityRepository.findByMobilePhone(SmsRule.defaultCcc(countryCallingCode), mobilePhoneNumber);
                    log.info("sendRegisterCaptcha phoneNumber {} isMobilePhoneNotInUse {}",
                            mobilePhoneNumber, !aggregate.isMobilePhoneInUse());
                    aggregate.checkMobilePhoneNotInUse();
                }
        );
    }

    /**
     * @param countryCallingCode
     * @param mobilePhoneNumber
     * @param captcha
     */
    @Override
    public void verifyMobilePhoneRegisterCaptcha(Integer countryCallingCode, Long mobilePhoneNumber, Integer captcha) {
        ValidateAssert.notNull(captcha, TipsConst.captcha_missing);
        ValidateAssert.isTrue(StringExtendsUtils.isMobilePhoneNumber(mobilePhoneNumber + ""), TipsConst.incorrect_mobile_phone_format);
        final RLock lock = redissonClient.getLock(RedisKeyConst.PHONE_CAPTCHA_LOCK_PREFIX + mobilePhoneNumber);
        try {
            boolean res = lock.tryLock();
            if (!res) {
                throw new ServiceException(BusinessState.DUPLICATE_REQUESTS);
            }
            countryCallingCode = SmsRule.defaultCcc(countryCallingCode);
            // 校验验证码
            smsBo.verifySmsCaptcha(countryCallingCode, mobilePhoneNumber, captcha, false);
            // 预创建用户安全
            String password = PasswordUtil.randomPassword(passwordLength);
            UserSecurityAggregate aggregate = userSecurityDomainService.preCreateOfMobilePhone(countryCallingCode,
                    mobilePhoneNumber, passwordEncoder.encode(password));
            // 创建用户账号
            userAccountDomainService.createOfMobilePhone(countryCallingCode, mobilePhoneNumber, aggregate.getRoot().getUserId());
            // 用户安全创建完成
            userSecurityDomainService.finishCreateOfMobile(aggregate);
            // 发送用户的密码到用户的注册手机
            smsBo.sendRegisterSuccessSms(countryCallingCode, mobilePhoneNumber, password, passwordEncoder.encode(password));
        } finally {
            if (null != lock && lock.isLocked()) { // 是否还是锁定状态
                if (lock.isHeldByCurrentThread()) {//是否是当前执行线程的锁
                    lock.unlock(); // 释放锁
                }
            }
        }
    }
}
